﻿
using static Toupcam;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection.Emit;

namespace Om.Camera.Toup;

public class ToupCamera : IBaseCamera
{
    private bool _isConnected;
    public bool IsConnected
    {
        get => _isConnected;
        private set
        {
            if (_isConnected != value)
            {
                _isConnected = value;
                ConnectionChanged?.Invoke(this, value);
            }
        }
    }
    public event Action<byte[]>? FrameReceived;
    public event EventHandler<bool>? ConnectionChanged;
    public string Id { get; private set; }

    private Dictionary<string,string> _dictCamIds = new Dictionary<string, string>();
    public async Task<List<string>> GetCameraList()
    {
        Toupcam.DeviceV2[] arr = Toupcam.EnumV2();
        List<string> list = new List<string>();
        foreach (var item in arr)
        {
            var displayname = item.displayname;
            if (_dictCamIds.ContainsKey(displayname))
                displayname += $"_{Guid.NewGuid().ToString().Substring(0, 4)}";

            _dictCamIds[displayname] = item.id;
            list.Add(displayname);
        }

        return list;
    }

    public async Task Connect(string cameraIpOrName)
    {
        var camId = _dictCamIds[cameraIpOrName];
        StartDevice(camId);
        Id = cameraIpOrName;
        IsConnected = true;
    }

    public async Task Disconnect()
    {
        _tcam.Close();
        _tcam = null;
        IsConnected = false;
    }

    public  async Task Capture(string fileName)
    {
        try
        {
            lock (_bmpLock)
            {
                var bmp = new Bitmap(_bmp);
                bmp.Save(fileName, ImageFormat.Jpeg);
            }
        }
        catch(Exception ex) { 
            
        }
    }
    public bool CanSetExposure => true;

    public async Task<bool> SetAutoExposure(bool isAutoExposure)
    {
        try
        {
            return _tcam.put_AutoExpoEnable(isAutoExposure);
        }
        catch (Exception e)
        {
            return false;
        }
    }

    public async Task<bool> SetExposureValue(int val)
    {
        try
        {
            var res = _tcam.put_AutoExpoEnable(false);
            res = _tcam.put_ExpoTime((uint)val);
            return res;
        }
        catch (Exception e)
        {
            return false;
        }
    }

    public async Task<int> GetExposureValue()
    {
        try
        {
            var res = _tcam.get_ExpoTime(out var time);
            if (res) return (int)time;
        }
        catch (Exception e)
        {
        }

        return 0;
    }
    #region MyRegion

    private Toupcam _tcam;
    private Timer _timer;
    private Bitmap _bmp = null; 
    private void StartDevice(string camId)
    {
        _tcam?.Close();

        _tcam = Toupcam.Open(camId);
        if (_tcam != null)
        {

            uint resnum = _tcam.ResolutionNumber;
            uint eSize = 0;
            if (_tcam.get_eSize(out eSize))
            {
                
                int width = 0, height = 0;
                if (_tcam.get_Size(out width, out height))
                {
                    /* The backend of Winform is GDI, which is different from WPF/UWP/WinUI's backend Direct3D/Direct2D.
                     * We use their respective native formats, Bgr24 in Winform, and Bgr32 in WPF/UWP/WinUI
                     */
                    _bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
                    if (!_tcam.StartPullModeWithCallback(new Toupcam.DelegateEventCallback(DelegateOnEventCallback)))
                        throw new Exception("StartPullModeWithCallback failed");
                    else
                    {
                        bool autoexpo = true;
                        _tcam.get_AutoExpoEnable(out autoexpo);
                    }
                }
            }

        }
    }
    private void DelegateOnEventCallback(Toupcam.eEVENT evt)
    {
        /* this is called by internal thread of toupcam.dll which is NOT the same of UI thread.
         * So we use BeginInvoke
         */
        if (_tcam != null)
        {
            try
            {
                switch (evt)
                {
                    case Toupcam.eEVENT.EVENT_ERROR:
                        OnEventError();
                        break;
                    case Toupcam.eEVENT.EVENT_DISCONNECTED:
                        OnEventDisconnected();
                        break;
                    case Toupcam.eEVENT.EVENT_EXPOSURE:
                        OnEventExposure();
                        break;
                    case Toupcam.eEVENT.EVENT_IMAGE:
                        OnEventImage();
                        break;
                    case Toupcam.eEVENT.EVENT_STILLIMAGE:
                        OnEventStillImage();
                        break;
                    case Toupcam.eEVENT.EVENT_TEMPTINT:
                        OnEventTempTint();
                        break;
                    default:
                        break;
                }
            }
            catch(Exception e)
            {

            }
        }
    }

    private void OnEventError()
    {
        _tcam.Close();
        _tcam = null;
        IsConnected = false;
    }

    private void OnEventDisconnected()
    {
        _tcam.Close();
        _tcam = null;
        IsConnected = false;
    }

    private void OnEventExposure()
    {
        //uint nTime = 0;
        //if (cam_.get_ExpoTime(out nTime))
        //{
        //    slider_expotime_.Value = (int)nTime;
        //    label_expotime_.Content = nTime.ToString();
        //}
    }


    private readonly object _bmpLock = new object(); // 新增锁对象

        private void OnEventImage()
    {
        if (_bmp != null)
        {
            lock (_bmpLock)
            {
                Toupcam.FrameInfoV4 info = new Toupcam.FrameInfoV4();
                bool bOK = false;
                try
                {
                    BitmapData bmpdata = _bmp.LockBits(new Rectangle(0, 0, _bmp.Width, _bmp.Height), ImageLockMode.WriteOnly, _bmp.PixelFormat);
                    try
                    {
                        bOK = _tcam.PullImage(bmpdata.Scan0, 0, 24, bmpdata.Stride, out info); // check the return value
                    }
                    finally
                    {
                        _bmp.UnlockBits(bmpdata);
                    }
                }
                catch (Exception ex)
                {

                }
                if (bOK)
                {
                    try
                    {
                        using var ms = new MemoryStream();
                        // 将 Bitmap 对象保存到 MemoryStream 中，指定图片格式
                        _bmp.Save(ms, ImageFormat.Jpeg);
                        // 将 MemoryStream 中的数据转换为 byte[]
                        FrameReceived?.Invoke(ms.ToArray());
                    }
                    catch(Exception ex2) { }
                }
            }

        }
    }
    private void OnEventStillImage()
    {
        //Toupcam.FrameInfoV4 info = new Toupcam.FrameInfoV4();
        //if (_tcam.PullImage(IntPtr.Zero, 1, 32, 0, out info))   /* peek the width and height */
        //{
        //    WriteableBitmap bmp = new WriteableBitmap((int)info.v3.width, (int)info.v3.height, 0, 0, PixelFormats.Bgr32, null);
        //    bool bOK = false;
        //    try
        //    {
        //        bmp.Lock();
        //        try
        //        {
        //            bOK = _tcam.PullImage(bmp.BackBuffer, 1, 32, bmp.BackBufferStride, out info); // check the return value
        //        }
        //        finally
        //        {
        //            bmp.Unlock();
        //        }
        //    }
        //    catch (Exception ex)
        //    {
        //        MessageBox.Show(ex.ToString());
        //    }
        //    if (bOK)
        //        SaveToFile(bmp);
        //}
    }
    private void OnEventTempTint()
    {
        //int nTemp = 0, nTint = 0;
        //if (_tcam.get_TempTint(out nTemp, out nTint))
        //{
        //    label_temp_.Content = nTemp.ToString();
        //    label_tint_.Content = nTint.ToString();
        //    slider_temp_.Value = nTemp;
        //    slider_tint_.Value = nTint;
        //}
    }


    #endregion
}